This document explains how ODF helps you manage the layout of your views when the frame is resized and how you can customize the default behaviors. This release provides a basic layout manager that handles simple cases automatically and provides virtual methods to override for other cases.
Table of Contents
----------------------
• View Bindings
• Special Case of Frames
• Special Values for Each Type of View
• Customizing the Layout of Views
• Example with Sibling Views
• Controling Maximum and Minimum Sizes
• Layout of Views in Resources
• API Reference
View Bindings
By default each instance of FW_CView is layed out according to its fBounds and fBindings attributes. The fBounds rectangle gives the position and size inside the parent view's content space. The flag fBindings represents its geometrical constraints, i.e. how the view's bounds should change when the extent of its parent view changes.
A view's fBindings is the combination of 6 bits which can be turned on or off using the constants defined in FWViews.k:
#define FW_kNoBinding 0x00000000
#define FW_kLeftBinding 0x00000001
#define FW_kRightBinding 0x00000002
#define FW_kTopBinding 0x00000004
#define FW_kBottomBinding 0x00000008
#define FW_kFixedWidth 0x00000010
#define FW_kFixedHeight 0x00000020
The Left/Right/Top/Bottom bits are on when the view stays at a fixed distance to the left/right/top/bottom edge of the parent view content. The Width and Height bits are on when the view's width or height is fixed (all horizontal constraints cannot be on at the same time and all vertical constraints cannot be on at the same time).
Notes:
• View bindings don't have any effect when changing the size or the location of this view with SetSize or SetLocation. They only apply to changes to the parent view.
• Changing the parent view location doesn't modify its subviews, only a change in the content size has any effect.
• When the parent view doesn't have any scrolling content, i.e. its extent is the same as its size, the picture above shows the actual parent view bounds. But if the parent view is a scrolling content view its visible bounds may be smaller in this picture.
Special Case of Frames
A frame is the root view of the hierarchy, the only view whose parent view is null. Its fBindings attribute is not used.
An embedded frame is not resized automatically by ODF when the size of its container changes, it is your responsibility. However you should never call SetSize directly on instances of FW_CFrame (FW_CFrame calls it internally in FrameShapeChanged). In order to control the size of your frame use FW_MProxy::ChangeFrameShapes or the OpenDoc method ODFrame::ChangeFrameShape.
Similarily, you are not allowed to call SetLocation for a frame.
Default Values for Each Type of View
FWViews.k defines the following additional constants for your convenience:
The constructor of FW_CView sets the default bindings to FW_kFixedBounds, i.e. most views should keep a fixed location and size when their parent is resized. The same default is kept in all subclasses of FW_CView except FW_CSuperView, FW_CScrollBar and FW_CGrowBox. Of course the correct behavior depends on your part's content, you can change the bindings by calling FW_CView::SetBindings.
The constructor of FW_CSuperView sets the default bindings to FW_kFitToEnclosure: this is generally what you need for a content view. The constructor of FW_CScrollBar uses FW_kHScrollBarBinding and FW_kVScrollBarBinding. The constructor of FW_CGrowBox uses FW_kGrowBoxBinding so that the grow box remains at the bottom right corner of the frame.
When declaring views in resources you must explicitely set the bindings value you want in the corresponding view resource type. For instance, to make an edit view grow horizontally with its parent use the value FW_kLeftBinding + FW_kRightBinding + FW_kTopBinding + FW_kFixedHeight.
Important:
• ODF view bindings are not enough to handle the layout of sibling views relative to each other. You must customize the layout manager as explained in the next section.
• A value of 0, i.e. no bindings at all, should be a rare case. It means that the view size and location will change proportionally with the parent's extent.
• Be careful when changing the default bindings for views deriving from FW_CControl. Buttons and popup menus should generally keep a fixed size. Scroll bars must keep their standard width in order to be displayed correctly.
Customizing the Layout of Views
When the constraints defined by the view bindings are not enough to handle your layout you can override the AdjustToNewLayout method of each view that requires it:
virtual void AdjustToNewLayout(Environment *ev,
const FW_CPoint& oldExtent,
const FW_CPoint& newExtent,
FW_Boolean refresh);
oldExtent = previous extent of the parent view.
oldExtent = new extent of the parent view, to which the view must adjust.
refresh = true if the modified regions should be updated.
This method is called by the parent view's SetExtent method since it where the change occurs. It is important to note that AdjustToNewLayout is not called directly by SetSize because what matters is the size of the view content (where subviews are layed out), not the size of the view bounds (i.e. its visible portion). However, SetSize ends up adjusting the view's extent with SetExtent in most cases except for scrolling views.
When overriding AdjustToNewLayout you decide the new location and size of the view inside its parent's content and then call SetLocation and SetSize. If the view is not scrolling and contains subviews SetSize will propagate the layout changes to the subviews through their own AdjustToNewLayout methods.
The Draw, Container, and Form samples show examples of custom layouts. In Form the content view is centered when the frame is bigger and anchored to the top left corner when the frame is smaller:
As mentioned in the previous section ODF view binding flags are not enough to handle the layout of sibling views relative to each other. In the following example a parent view contains 3 subviews. Views 1 and 2 keep a fixed height and an equal width when the parent's content grows, their method AdjustToNewLayout must be customized (*). On the other hand view 3 keeps a fixed location and grows to follow the parent's content: set its bindings to FW_kFitToEnclosure to implement this behavior.
(*) It may seem that using FW_kLeftBinding for view 1 and FW_kRightBinding for view 2 is enough to do the job horizontally, since this will keep their width equal. But there is a problem with that approach, the separation between the views won't be fixed anymore , in fact the views will overlap when the content grows too big.
The following listings show an implementation for the view's AdjustToNewLayout methods. The calculations are different but the it could be combined into one method if the views belong to the same class. Also, the code shows what can be done to handle "limit conditions", i.e. when views becomes too small or too big. You can also control this somewhat with the AdjustZoomedWindowSize and AdjustWindowGrowLimits as explained in the next section.
SetVisible(ev, true, true); // Makes view visible again
SetSize(ev, size, refresh);
}
return;
}
Controling Maximum and Minimum Sizes
Warning: an embedded OpenDoc frame can have its size enlarged or reduced to any value by its container part. This means that you cannot assume anything on the limits of your frame's size, and thus you cannot assume that its views won't be adjusted to any arbitrary small or large value; in theory the embedded frame can negociate its size once but it cannot do anything if the container is stubborn! (However this doesn't apply to subviews belonging to a scrolling content view, since the content size is not controlled by the container part).
The consequence is that you should handle "limit conditions" in your code, especially for very small views. In the previous example views are made invisible instead of getting a small or negative size.
ODF provides you with one small relief, an API to set the limits on your window's grow and zoom sizes. This helps control the maximum and minimum sizes of root frames. All you need to do is implement the following virtual methods in your frame class and update the arguments passed as reference:
A frame or a superview can be defined in a resource file with a type FW_RFrameLayout or FW_RViewLayout. The first resource field is a point representing the layout size followed by the list of subviews. For instance Form's Views.fr contains this:
The layout size {H,V} is like a virtual extent (not the actual extent of the frame created from this resource!). Its values are not important, they are only used as a reference for the subviews defined below. For instance the scroll bars and grow box are placed at the edges of this layout, with the correct 1 pixel overlap. When the resource is loaded at runtime the layout size is replaced by the actual extent of the frame which has the effect of adjusting each subview's position and size according to their bindings.
In the example above RFormView is a scrolling content view. Because its layout is customized by program the bounds {0,0,H,V} declared here don't have any significance, they will be reset after the view is created. On the other hand the extent field defines the size of the content itself, it is independent of the view's bounds or the frame's layout. Below, each subview inside the content, such as the edit view shown here, has its bounds defined in content coordinates. They are not affected by the frame size either.
SetSize modifies the visible bounds of a view inside its parent's content. If refresh is true it updates the affected regions (either the whole area oldSize + newSize if the view's ForceResizeRedraw flag is off, or only the region that changed if the flag is on). See also SetForceResizeRedraw.
For a non-scrolling superview SetSize also calls SetExtent to keep its extent similar to its size. This in turn adjusts the layout of its subviews.
You should never call SetSize directly on instances of FW_CFrame. Use FW_MProxy::ChangeFrameShapes to control the size of your frame.
SetSize should not be overriden by your view classes. Override instead the SizeChanged virtual method called by SetSize.
SetLocation moves the visible bounds of a view inside its parent's content. If refresh is true it updates the affected regions. SetLocation called on a superview doesn't affect the layout of its subviews, it doesn't modify the superview's extent.
You are not allowed to call SetLocation directly on instances of FW_CFrame. A frame's location is always 0,0 since it is the root view of its own views hierarchy. Use other methods to move an embedded frame inside your part.
SetLocation should not be overriden by your view classes. Override instead the LocationChanged virtual method called by SetLocation.
This virtual method is empty by default and called by SetSize with the old view size after it has been changed (the new size is simply returned by GetSize). You can override it for any purpose. However it is not intended to let you managethe layout of your views, override AdjustToNewLayout instead.
This virtual method is empty by default and called by SetLocation with the old view location after it has been changed (the new location is simply returned by GetLocation). You can override it for any purpose. However it is not intended to let you managethe layout of your views, override AdjustToNewLayout instead.
SetExtent is a method of FW_CSuperView (simple views don't have any extent). It modifies a superview's extent and adjust its subviews by calling their AdjustToNewLayout methods. For the frame's content view SetExtent also updates the ODFrame's extent and adjust the scroller.
In general you don't need to call SetExtent in your program. Frames are created with an extent equal to their size by default. The superview's extent can be declared in the constructor or resource field, otherwise it is also set to its size.
virtual void AdjustToNewLayout(Environment *ev,
const FW_CPoint& oldExtent,
const FW_CPoint& newExtent,
FW_Boolean refresh);
oldExtent = previous extent of the parent view.
oldExtent = new extent of the parent view, to which the view must adjust to.
refresh = true if the modified regions should be updated.
This method is called by the parent view's SetExtent method when the extent changes. See sections above for more information.
This method lets you change the fResizeForceRedraw flag in FW_CView objects. When this flag is true the entire view is refreshed if its size changed, when it is false only the modified regions are refreshed. By default FW_CView objects are created with fResizeForceRedraw set to true and FW_CSuperView objects have it set to false: in general the drawing of a simple view's content depends on its size, but not the drawing of a superview's content (in this case the subviews don't count because the superview's content is updated automatically when their layout is adjusted). You must inverse the flag if this behavior is not rigtht for you.
Notes:
• SincefResizeForceRedraw is not a view resource field you must call SetResizeForceRedraw in your PostCreatViewFromStream method.
• Another way of overriding this flag is to call Invalidate(ev) in your view's SizeChanged method. Or for a frame to call Invalidate(ev) in its FrameShapeChanged method.